package ooo.reindeer.concurrent.filequeue.log;

import ooo.reindeer.concurrent.filequeue.exception.FileEOFException;
import ooo.reindeer.concurrent.filequeue.exception.FileFormatException;
import ooo.reindeer.concurrent.filequeue.util.MappedByteBufferUtil;
import ooo.reindeer.logging.ILogger;
import ooo.reindeer.logging.LogUtil;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


public class LogEntity {
    public static final byte WRITE_SUCCESS = 1;
    public static final byte WRITE_FAILURE = 2;
    public static final byte WRITE_FULL = 3;
    public static final String MAGIC = "FQueuefs";
    private static final ILogger logger = LogUtil.getLogger(LogEntity.class);
    public static int messageStartPosition = 24;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    public MappedByteBuffer mappedByteBuffer;
    private File file;
    private RandomAccessFile raFile;
    private FileChannel fc;
    private int fileLimitLength = 4 * 1024;

    private LogIndex db = null;


    private String magicString = null;
    private int version = -1;
    private int readerPosition = -1;
    private int writerPosition = -1;
    private long nextFile = -1;
    private int endPosition = -1;
    private long currentFileNumber = -1;

    public LogEntity(String path, LogIndex db, long fileNumber,
                     int fileLimitLength) throws IOException, FileFormatException {
        this.currentFileNumber = fileNumber;
        this.fileLimitLength = fileLimitLength;
        this.db = db;
        file = new File(path);
        // 文件不存在，创建文件
        if (file.exists() == false) {
            createLogEntity();

//			FileRunner.addCreateFile(Long.toString(System.currentTimeMillis()));
        } else {
            raFile = new RandomAccessFile(file, "rwd");
            if (raFile.length() < LogEntity.messageStartPosition) {
                throw new FileFormatException("file format error");
            }
            fc = raFile.getChannel();
            mappedByteBuffer = fc.map(MapMode.READ_WRITE, 0,
                    this.fileLimitLength);
            // magicString
            byte[] b = new byte[8];
            mappedByteBuffer.get(b);
            magicString = new String(b);
            if (magicString.equals(MAGIC) == false) {
                throw new FileFormatException("file format error");
            }
            // version
            version = mappedByteBuffer.getInt();
            // nextfile
            nextFile = mappedByteBuffer.getLong();
            endPosition = mappedByteBuffer.getInt();
            // 未写满
            if (endPosition == -1) {
                this.writerPosition = db.getWriterPosition();
            } else if (endPosition == -2) {// 预分配的文件
                this.writerPosition = LogEntity.messageStartPosition;
                db.putWriterPosition(this.writerPosition);
                mappedByteBuffer.position(20);
                mappedByteBuffer.putInt(-1);
                this.endPosition = -1;

            } else {
                this.writerPosition = endPosition;
            }
            if (db.getReaderIndex() == this.currentFileNumber) {
                this.readerPosition = db.getReaderPosition();
            } else {
                this.readerPosition = LogEntity.messageStartPosition;
            }
        }
        executor.execute(new Sync());

    }

    public void close() {
        try {
            if (mappedByteBuffer == null) {
                return;
            }
            mappedByteBuffer.force();
            MappedByteBufferUtil.clean(mappedByteBuffer);

            mappedByteBuffer = null;
            executor.shutdown();
            fc.close();
            raFile.close();
        } catch (IOException e) {
            logger.error("close logentity file error:", e);
        }
    }

    private boolean createLogEntity() throws IOException {
        if (file.createNewFile() == false) {
            return false;
        }
        raFile = new RandomAccessFile(file, "rwd");
        fc = raFile.getChannel();
        mappedByteBuffer = fc.map(MapMode.READ_WRITE, 0, this.fileLimitLength);
        mappedByteBuffer.put(MAGIC.getBytes());
        mappedByteBuffer.putInt(version);// 8 version
        mappedByteBuffer.putLong(nextFile);// 16next fileindex
        mappedByteBuffer.putInt(endPosition);// 20
        mappedByteBuffer.force();
        this.magicString = MAGIC;
        this.writerPosition = LogEntity.messageStartPosition;
        this.readerPosition = LogEntity.messageStartPosition;
        db.putWriterPosition(this.writerPosition);
        return true;
    }

    public long getCurrentFileNumber() {
        return this.currentFileNumber;
    }

    public long getNextFile() {
        return this.nextFile;
    }

    public String headerInfo() {
        return toString();
    }

    @Override
    public String toString() {
        return "LogEntity{" +
                "fileLimitLength=" + fileLimitLength +
                ", magicString='" + magicString + '\'' +
                ", version=" + version +
                ", readerPosition=" + readerPosition +
                ", writerPosition=" + writerPosition +
                ", nextFile=" + nextFile +
                ", endPosition=" + endPosition +
                ", currentFileNumber=" + currentFileNumber +
                '}';
    }

    public boolean isFull(int increment) {
        // confirm if the file is full
        return this.fileLimitLength < this.writerPosition + increment;
    }


    public void putNextFile(Long number) {
        mappedByteBuffer.position(12);
        mappedByteBuffer.putLong(number);
        this.nextFile = number;
    }

    private void putReaderPosition(int pos) {
        db.putReaderPosition(pos);
    }


    private void putWriterPosition(int pos) {
        db.putWriterPosition(pos);
    }

    public byte[] readNextAndRemove() throws FileEOFException {
        if (this.endPosition != -1 && this.readerPosition >= this.endPosition) {
            throw new FileEOFException("file eof");
        }

        // readerPosition must be less than writerPosition
        if (this.readerPosition >= this.writerPosition) {
            return null;
        }
        mappedByteBuffer.position(this.readerPosition);
        int length = mappedByteBuffer.getInt();
        byte[] b = new byte[length];
        this.readerPosition += length + 4;
        mappedByteBuffer.get(b);
        putReaderPosition(this.readerPosition);
        return b;
    }

    public byte write(byte[] log) {
        int increment = log.length + 4;
        if (isFull(increment)) {
            mappedByteBuffer.position(20);
            mappedByteBuffer.putInt(this.writerPosition);
            this.endPosition = this.writerPosition;
            return WRITE_FULL;
        }
        mappedByteBuffer.position(this.writerPosition);
        mappedByteBuffer.putInt(log.length);
        mappedByteBuffer.put(log);
        this.writerPosition += increment;
        putWriterPosition(this.writerPosition);
        return WRITE_SUCCESS;
    }

    public class Sync implements Runnable {
        @Override
        public void run() {
            while (true) {
                if (mappedByteBuffer != null) {
                    try {
                        mappedByteBuffer.force();
                    } catch (Exception e) {
                        break;
                    }
                    try {
                        TimeUnit.SECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        break;
                    }
                } else {
                    break;
                }
            }

        }

    }

}
